package jp.ddo.trismegistos.dao.impl;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletContext;

import jp.ddo.trismegistos.RsStatics;
import jp.ddo.trismegistos.dao.DaoManager;
import jp.ddo.trismegistos.exception.RsException;
import jp.ddo.trismegistos.util.SqlFileReader;

import org.apache.log4j.Logger;

/**
 * H2データベース用のDaoマネージャ実装クラス。
 * 
 * @author Nagai Satoshi
 */
public class H2DaoManagerImpl implements DaoManager {

	/** ログ */
	private static final Logger log = Logger.getLogger(H2DaoManagerImpl.class
			.getName());

	/** JdbcURI */
	private String jdbcUri;

	/** ユーザ */
	private String dbUser;

	/** パスワード */
	private String dbPassword;

	/** SQLマッピング */
	private Map<String, String> sqlMap;

	/**
	 * 引数に指定したコンテキストを元に新しいDaoマネージャを生成します。
	 * 
	 * @param context
	 *            コンテキスト
	 * @throws IllegalArgumentException
	 *             引数のコンテキストが{@code null}の場合
	 */
	public H2DaoManagerImpl(final ServletContext context)
			throws IllegalArgumentException {
		if (context == null) {
			throw new IllegalArgumentException("Context is null.");
		}
		final Map<String, String> master = getMaster(context);
		if (log.isDebugEnabled()) {
			log.debug("DaoManager initializing start.");
		}
		jdbcUri = master.get(RsStatics.JDBC_URI);
		dbUser = master.get(RsStatics.DB_USER);
		dbPassword = master.get(RsStatics.DB_PASSWORD);
		if (log.isDebugEnabled()) {
			log.debug("DB connecting info : [URI : " + jdbcUri + ", User : "
					+ dbUser + "]");
		}
		final String encoding = master.get(RsStatics.ENCODING);
		final String sqlFileDirPath = context.getRealPath(master
				.get(RsStatics.SQL_FILE_DIR_PATH));
		if (log.isDebugEnabled()) {
			log.debug("Read sql files start.");
		}
		sqlMap = createSqlMap(sqlFileDirPath, encoding);
		if (log.isDebugEnabled()) {
			log.debug("Read sql files end.");
			log.debug("DaoManager initializing end.");
		}
	}

	/**
	 * コンテキストからマスタ情報を取得して返します。
	 * 
	 * @param context
	 *            コンテキスト
	 * @return マスタ情報
	 */
	@SuppressWarnings("unchecked")
	private Map<String, String> getMaster(final ServletContext context) {
		return (Map<String, String>) context.getAttribute(RsStatics.MASTER);
	}

	/**
	 * SQLファイルディレクトリ内のすべてのSQLファイル内容を読み込み、SQLファイル名（拡張子除く）をIDとしてSQL
	 * IDとSQLのマッピングを作成します。
	 * 
	 * @param sqlFileDirPath
	 *            SQLファイルディレクトリの絶対パス
	 * @param encoding
	 *            エンコーディング
	 * @return SQLマッピング
	 * @throws RsException
	 *             SQLディレクトリが正常なディレクトリでなかった場合
	 */
	private Map<String, String> createSqlMap(final String sqlFileDirPath,
			final String encoding) throws RsException {
		final File sqlFileDir = new File(sqlFileDirPath);
		if (sqlFileDir.isDirectory() == false) {
			throw new RsException("No such directory : "
					+ sqlFileDir.getAbsolutePath());
		}
		return createSqlMap(sqlFileDir, encoding);
	}

	/**
	 * SQLファイルディレクトリ内のすべてのSQLファイル内容を読み込み、SQLファイル名（拡張子除く）をIDとしてSQL
	 * IDとSQLのマッピングを作成します。
	 * 
	 * @param sqlFileDir
	 *            SQLファイルディレクトリオブジェクト
	 * @param encoding
	 *            エンコーディング
	 * @return SQLマッピング
	 */
	private Map<String, String> createSqlMap(final File sqlFileDir,
			final String encoding) {
		final Map<String, String> sqlMap = new HashMap<String, String>();
		final String[] sqlFileNames = sqlFileDir.list(new SqlFilenameFilter());
		final String sqlFileDirPath = sqlFileDir.getAbsolutePath();
		for (final String sqlFileName : sqlFileNames) {
			final String sqlId = getSqlId(sqlFileName);
			final String sqlFilePath = getSqlFilePath(sqlFileDirPath,
					sqlFileName);
			final String sql = readSqlFile(sqlFilePath, encoding);
			if (log.isDebugEnabled()) {
				log.debug("Read sql file : " + sqlFilePath);
				log.debug("SQL ID : " + sqlId);
				log.debug("SQL : " + sql);
			}
			sqlMap.put(sqlId, sql);
		}
		return Collections.unmodifiableMap(sqlMap);
	}

	/**
	 * 引数に渡されたSQLファイル名からSQL IDを作成します。
	 * 
	 * @param sqlFileName
	 *            SQLファイル名
	 * @return SQL ID
	 */
	private String getSqlId(final String sqlFileName) {
		return sqlFileName.substring(0,
				sqlFileName.lastIndexOf(RsStatics.CHAR_DOT));
	}

	/**
	 * 引数に渡されたSQLファイルディレクトリパス、SQLファイル名からSQLファイルの絶対パスを作成します。
	 * 
	 * @param sqlFileDirPath
	 *            SQLファイルディレクトリの絶対パス
	 * @param sqlFileName
	 *            SQLファイル名
	 * @return SQLファイルの絶対パス
	 */
	private String getSqlFilePath(final String sqlFileDirPath,
			final String sqlFileName) {
		return sqlFileDirPath + File.separator + sqlFileName;
	}

	/**
	 * 引数に渡されたSQLファイル内容を読み込み、SQLを返します。
	 * 
	 * @param sqlFilePath
	 *            SQLファイルの絶対パス
	 * @param encoding
	 *            エンコーディング
	 * @return SQL
	 */
	private String readSqlFile(final String sqlFilePath, final String encoding) {
		return SqlFileReader.read(sqlFilePath, encoding);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see jp.ddo.trismegistos.dao.DaoManager#getJdbcUri()
	 */
	@Override
	public String getJdbcUri() {
		return jdbcUri;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see jp.ddo.trismegistos.dao.DaoManager#getDbUser()
	 */
	@Override
	public String getDbUser() {
		return dbUser;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see jp.ddo.trismegistos.dao.DaoManager#getDbPassword()
	 */
	@Override
	public String getDbPassword() {
		return dbPassword;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see jp.ddo.trismegistos.dao.DaoManager#getSqlMap()
	 */
	@Override
	public Map<String, String> getSqlMap() {
		return sqlMap;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @throws NullPointerException
	 *             引数が{@code null}の場合
	 * @throws RsException
	 *             引数に指定された{@code sqlId}に紐付くSQLが存在しない場合
	 * @see jp.ddo.trismegistos.dao.DaoManager#getSql(java.lang.String)
	 */
	@Override
	public String getSql(final String sqlId) throws NullPointerException,
			RsException {
		if (sqlId == null) {
			throw new NullPointerException("SQL ID is null.");
		}
		final String sql = sqlMap.get(sqlId);
		if (sql == null) {
			throw new RsException("No such SQL : " + sqlId);
		}
		return sql;
	}

	/**
	 * SQLファイルのみをフィルタリングする{@code FilenameFilter}
	 * 
	 * @author Nagai Satoshi
	 */
	static class SqlFilenameFilter implements FilenameFilter {

		/** SQLファイルの拡張子 */
		public static final String SQL_FILE_EXTENSION = ".sql";

		/**
		 * {@inheritDoc}
		 * 
		 * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
		 */
		@Override
		public boolean accept(final File arg0, final String arg1) {
			return arg1.endsWith(SQL_FILE_EXTENSION);
		}
	}
}
